﻿
// MFCApplication3Dlg.cpp: 实现文件
//

//#define _CRT_SECURE_NO_DEPRECATE

#include "stdafx.h"
#include"MFCApplication3Dlg.h"
#include<windows.h>
#include<math.h>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
using namespace cv::ml;
using namespace face;


static string imgsrc;
Mat imagedst;
vector<Mat> images;//训练图片容器
vector<String> labels;//每张图片对应姓名（有重复）（aa,aa,aa,bb,bb,cc,cc,cc）
vector<string> labels_name;//不重复的名(aa,bb,cc)
vector<int> name_index;//不重复名对应的下标(0,1,2)
vector<int> labels_index;//有重复的名对应的下标（0,0,0,1,1,2,2,2）；
int result;//预测结果
CString inputstr;//文本框输入姓名
int n = 1;//给照片赋名字
Mat imgs;//拍照的照片

#include<windows.h>

vector<string> outname;//存放出勤人名字
String face_cascade_name = "haarcascade_frontalface_alt.xml";//人脸特征库
CascadeClassifier face_cascade;//级联分类器

vector<Mat> imglist;//存放图中识别出的人脸图片
Mat frame;


#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMFCApplication3Dlg 对话框



CMFCApplication3Dlg::CMFCApplication3Dlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MFCAPPLICATION3_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication3Dlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_EDIT1, m_edit);
	//DDX_Control(pDX, IDC_SHIPIN, m_brush);
}

BEGIN_MESSAGE_MAP(CMFCApplication3Dlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_VIDEO, &CMFCApplication3Dlg::OnBnClickedVideo)
	ON_BN_CLICKED(IDC_TAKEPHOTO, &CMFCApplication3Dlg::OnBnClickedTakephoto)
	ON_BN_CLICKED(IDC_INPUTINFO, &CMFCApplication3Dlg::OnBnClickedInputinfo)
	ON_BN_CLICKED(IDC_INPUT_PIC, &CMFCApplication3Dlg::OnBnClickedInputPic)
	ON_BN_CLICKED(IDC_PIPEI, &CMFCApplication3Dlg::OnBnClickedPipei)
	ON_STN_CLICKED(IDC_SHIPIN, &CMFCApplication3Dlg::OnStnClickedShipin)
	ON_EN_CHANGE(IDC_EDIT1, &CMFCApplication3Dlg::OnEnChangeEdit1)
	ON_EN_CHANGE(IDC_INPUTNAME3, &CMFCApplication3Dlg::OnEnChangeInputname3)
	ON_WM_CTLCOLOR()
	
	ON_BN_CLICKED(IDC_CLOSE, &CMFCApplication3Dlg::OnBnClickedClose)
	ON_STN_CLICKED(IDC_STATIC1, &CMFCApplication3Dlg::OnStnClickedStatic1)
END_MESSAGE_MAP()


// CMFCApplication3Dlg 消息处理程序

BOOL CMFCApplication3Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 将“关于...”菜单项添加到系统菜单中。
	//界面背景颜色
	//m_brush.CreateSolidBrush(RGB(255, 255, 105));
	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);
	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时，框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	namedWindow("view", WINDOW_AUTOSIZE);
	HWND hWnd = (HWND)cvGetWindowHandle("view");
	HWND hParent = ::GetParent(hWnd);
	::SetParent(hWnd, GetDlgItem(IDC_STATIC1)->m_hWnd);
	::ShowWindow(hParent, SW_HIDE);


	namedWindow("view1", WINDOW_AUTOSIZE);
	HWND hWnd1 = (HWND)cvGetWindowHandle("view1");
	HWND hParent1 = ::GetParent(hWnd1);
	::SetParent(hWnd1, GetDlgItem(IDC_SHIPIN)->m_hWnd);
	::ShowWindow(hParent1, SW_HIDE);




	return TRUE;  // 除非将焦点设置到控件，否则返回 TRUE
}

void CMFCApplication3Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 如果向对话框添加最小化按钮，则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序，
//  这将由框架自动完成。

void CMFCApplication3Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
		CRect rc;
		GetWindowRect(&rc);
		CImage mImage;
		if (mImage.Load(_T("项目背景.jpg")) == S_OK)
		{
			//这里让图片绘制区域和窗口大小一致
			mImage.Draw(GetDC()->GetSafeHdc(), CRect(0, 0, rc.Width(), rc.Height()));
		}
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication3Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


int flag = true;//控制摄像头的开关
void CMFCApplication3Dlg::OnBnClickedVideo()
{
	// TODO: 在此添加控件通知处理程序代码
	flag = true;
	VideoCapture capture(0);
	Mat frame;
	while (flag)
	{
		capture >> frame;
		Mat imagedst;
		//以下操作获取图形控件尺寸并以此改变图片尺寸
		CRect rect;
		GetDlgItem(IDC_SHIPIN)->GetClientRect(&rect);
		Rect dst(rect.left, rect.top, rect.right, rect.bottom);
		resize(frame, imagedst, cv::Size(rect.Width(), rect.Height()));

		imshow("view1", imagedst);//显示视频到左侧图形控件
		imagedst.copyTo(imgs);//将此帧图片作为照片
		waitKey(30);
		n++;//以n的值为图片命名，防止重名覆盖
	}
}


void CMFCApplication3Dlg::OnBnClickedTakephoto()
{
	Mat img = imgs;
	Mat imagedst;
	//以下操作获取图形控件尺寸并以此改变图片尺寸  
	CRect rect;
	GetDlgItem(IDC_STATIC1)->GetClientRect(&rect);
	Rect dst(rect.left, rect.top, rect.right, rect.bottom);
	resize(img, imagedst, cv::Size(rect.Width(), rect.Height()));

	imshow("view", imagedst);

	//给照片取名，名字为帧数
	stringstream str;
	string str1;
	str << n;
	str >> str1;

	//创建子文件夹存放照片，子文件夹名为姓名
	CString path = "C:\\Users\\非凡\\Desktop\\采集照片\\" + inputstr + "\\";
	CString csPath(TEXT(path));
	if (!PathIsDirectory(path)) {
		CreateDirectory(csPath, 0);
	}

	//将拍照的照片放入指定子文件夹
	string str2 = "C:\\Users\\非凡\\Desktop\\采集照片\\"+inputstr+"\\";
	string str3 = ".jpg";
	string str4 = str2 + str1 + str3;
	imwrite(str4, imagedst);

}


void CMFCApplication3Dlg::OnBnClickedInputinfo()
{
	// TODO: 在此添加控件通知处理程序代码
	MYSQL mysql;    //一个数据库结构体
	MYSQL_RES* res; //一个结果集结构体
	MYSQL_ROW row;  //char** 二维数组，存放一条条记录
	//初始化数据库
	mysql_init(&mysql);
	//设置编码方式
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
	//连接数据库
	//判断如果连接失败就输出连接失败。
	if (mysql_real_connect(&mysql, "localhost", "root", "wangfeifan", "renlian", 3306, NULL, 0) == NULL)
		printf("连接失败！\\n");

	//获取结果集
	res = mysql_use_result(&mysql);

	
	String dir_path = "C:/Users/非凡/Desktop/训练文件/";//
	vector<String> fileNames;
	glob(dir_path, fileNames, true);//获取文件夹中文件的名（有递归）

	for (unsigned int frame = 0; frame < fileNames.size(); ++frame) {//image_file.size()代表文件中总共的图片个数
		Mat image = cv::imread(fileNames[frame]);
		cout << fileNames[frame] << endl;

		//正则获取名字
		const regex pattern(".*文件.{1}(.*)\\\\.*$");
		smatch results;
		string a = fileNames[frame];
		string s;
		if (regex_match(a, results, pattern)) {
			for (size_t i = 1; i < results.size(); ++i) {
				s += results[i];
			}
			cout << s;
		}
		string strContent = fileNames[frame];
		string_replace(strContent, "\\", "\\\\"); //将\转为\\,数据库不能识别"\"
		string pic_id = to_string(frame + 1);//数据库主键ID
		string pic_name = s;//数据库图片名
		string pic_add = strContent;//数据库图片地址

		String str = "INSERT INTO pic_table VALUES ( '" + pic_id + "','" + pic_name + "','" + pic_add + "' )";
		mysql_query(&mysql, str.c_str());//用sql语句添加数据
	}
	//释放结果集
	mysql_free_result(res);
	//关闭数据库
	mysql_close(&mysql);
}

//字符串替换函数
void CMFCApplication3Dlg::string_replace(std::string &s1, const std::string &s2, const std::string &s3)
{
	std::string::size_type pos = 0;
	std::string::size_type a = s2.size();
	std::string::size_type b = s3.size();
	while ((pos = s1.find(s2, pos)) != string::npos)
	{
		s1.replace(pos, a, s3);
		pos += b;
	}
}


void CMFCApplication3Dlg::OnBnClickedInputPic()
{
	// TODO: 在此添加控件通知处理程序代码
	CString picPath;
	string picpath;
	// TODO: 在此添加控件通知处理程序代码
	  //定义图片路径变量  
	CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY |
		OFN_OVERWRITEPROMPT | OFN_ALLOWMULTISELECT, NULL, this);  //选择文件对话框  
	if (dlg.DoModal() == IDOK)
	{
		picPath = dlg.GetPathName();  //获取图片路径  
		picpath = picPath.GetBuffer(0);
		imgsrc = picpath;
	}

	//cstring to string  使用这个方法记得字符集选用“使用多字节字符”，不然会报错  	
	imagedst = imread(imgsrc);
	imagedst.copyTo(frame);
	//以下操作获取图形控件尺寸并以此改变图片尺寸  
	CRect rect;
	GetDlgItem(IDC_STATIC1)->GetClientRect(&rect);
	//Rect dst(rect.left, rect.top, rect.right, rect.bottom);
	resize(imagedst, imagedst, cv::Size(rect.Width(), rect.Height()));
	imshow("view", imagedst);
	waitKey(0);
}



void CMFCApplication3Dlg::data_train() {

	// TODO: 在此添加控件通知处理程序代码
	MYSQL mysql;    //一个数据库结构体
	MYSQL_RES* res; //一个结果集结构体
	MYSQL_ROW row;  //char** 二维数组，存放一条条记录
	//初始化数据库
	mysql_init(&mysql);
	//设置编码方式
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
	//连接数据库
	//判断如果连接失败就输出连接失败。
	if (mysql_real_connect(&mysql, "localhost", "root", "wangfeifan", "renlian", 3306, NULL, 0) == NULL)
		printf("连接失败！\\n");
	//查询数据
	mysql_query(&mysql, "select * from pic_table");

	//获取结果集
	res = mysql_use_result(&mysql);
	//显示数据
	cout << "pic_id" << "\t" << "pic_name" << "\t" << "pic_add" << "\n" << endl;

	int i = 0;

	while (row = mysql_fetch_row(res))
	{
		i += 1;
		cout << row[0] << endl;//显示id
		cout << row[1] << endl;//显示姓名
		cout << row[2] << endl;//显示地址
		Mat pic = imread(row[2]);//获取图片
		Mat dst;
	
		cvtColor(pic, dst, CV_BGR2GRAY);
		equalizeHist(dst,dst);
		resize(dst, dst,Size(400, 500));

		images.push_back(dst);//图片加入训练集
		labels.push_back(row[1]);//标签加入训练集
	}

	cout << endl;

	//不重复的名
	labels_name.push_back(labels[0]);
	for (unsigned int frame = 0; frame < labels.size(); ++frame) {
		cout << labels[frame];
		if (frame < labels.size() - 1) {
			if (labels[frame + 1] != labels[frame]) {
				labels_name.push_back(labels[frame + 1]);
			}
		}
	}

	//每张图片对应的下标 有重复
	//训练时要输入下标，需要把名字转为数字
	labels_index.push_back(0);
	int sum = 0;
	for (unsigned int frame = 0; frame < labels.size(); ++frame) {
		if (frame > 0) {
			if (labels[frame - 1] != labels[frame]) {
				sum += 1;
				labels_index.push_back(sum);
			}
			else {
				labels_index.push_back(sum);
			}
		}
	}


	//释放结果集
	mysql_free_result(res);
	//关闭数据库
	mysql_close(&mysql);
	
}

//获得测试图片中所有人脸
void CMFCApplication3Dlg::detectAndDisplay()
{

	Mat frame_gray;	//接收灰度图
	std::vector<Rect> faces;
	cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
	equalizeHist(frame_gray, frame_gray);//直方图均衡化
	//获取测试图中的人脸
	face_cascade.detectMultiScale(frame_gray, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(160,160));

	for (size_t i = 0; i < faces.size(); i++)
	{
		//人脸画圆
		Point center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);
		ellipse(frame, center, Size(faces[i].width / 2, faces[i].height / 2), 0, 0, 360, Scalar(255, 0, 0), 2, 8, 0);

		Mat img2;
		img2 = frame(Rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height));
	
		resize(img2, img2,Size(400,500),(0, 0), (0, 0), 3);
		imglist.push_back(img2);

		//stringstream aa;
		//string bb;
		//aa << i;
		//aa >> bb;
		//imshow(bb, img2);
	}
}


//输出测试图片中识别出的人脸
void CMFCApplication3Dlg::print()
{
	CascadeClassifier cas("haarcascade_frontalface_default.xml");    //加载人脸分类器
	//特征脸方法主要是基于PCA降维实现
	Ptr<EigenFaceRecognizer> fc = EigenFaceRecognizer::create(100,30000);//创建人脸识别对象
	
	
	fc->train(images, labels_index);    //训练
	for (int i = 0; i < imglist.size(); i++)
	{
		int count = 0;
		Mat test(400, 500, CV_8UC1);
		Mat gray;
		int x = 0, y = 0;
		Mat roi = imglist[i];
		cvtColor(roi, gray, CV_BGR2GRAY);
		resize(gray, test, Size(400, 500));
		//训练过程
		result = fc->predict(test);
		//将训练结果转回为姓名，存入outname中
		if (result != -1)
		{
			if (outname.empty())
			{
				outname.push_back(labels_name[result]);

			}
			else
			{
				for (int i = 0; i < outname.size(); i++)
				{
					if (outname[i] == labels_name[result])
					{
						++count;
					}
				}
				if (count == 0)
				{
					outname.push_back(labels_name[result]);
				}
			}

		}
	}
}

void CMFCApplication3Dlg::OnBnClickedPipei()
{
	if (!face_cascade.load(face_cascade_name)) { printf("--(!)Error loading\n"); };//判断人脸检测库是否加载成功
	//得到存放图片和标签的两个vector
	data_train();
	//获得测试图片中所有人脸
	detectAndDisplay();
	//训练并输出
	print();
	//显示结果
	OnEnChangeEdit1();
}

void CMFCApplication3Dlg::OnStnClickedShipin()
{
	// TODO: 在此添加控件通知处理程序代码
}



void CMFCApplication3Dlg::OnEnChangeEdit1(){
	
	CString allName = "应到人员：\r\n";
	string everyOne;
	for (int i = 0; i < labels_name.size(); i++)
	{
		everyOne = labels_name[i]+"\t";
		if (i % 3 == 2) {
			everyOne+="\r\n";
		}
		allName += everyOne.c_str();
	}//循环输出全体人员


	CString namestr = allName+"\r\n"+"出勤人员：\r\n";
	string name;
	for (int i = 0; i < outname.size(); i++)
	{
		name = outname[i]+"\r\n";
		namestr += name.c_str();
	}//循环输出出勤人员
	
	namestr += "\r\n";

	//获得出勤率
	double chuqin_num = outname.size();
	double chuqinlv = chuqin_num / labels_name.size()*100;
	chuqinlv = floor(chuqinlv*10.0 + 0.5) / 10.0;//保留小数点后一位

	stringstream sss;
	string s_chuqinlv;
	sss << chuqinlv;
	sss >> s_chuqinlv;
	string s;
	s="出勤率：" + s_chuqinlv+"%";
	namestr += s.c_str();

	m_edit.SetWindowText(namestr);

}


void CMFCApplication3Dlg::OnEnChangeInputname3()
{
	//输入框内容
	CString str;
	GetDlgItem(IDC_INPUTNAME3)->GetWindowText(str);
	GetDlgItemText(IDC_INPUTNAME3, inputstr);

}



void CMFCApplication3Dlg::OnBnClickedClose()
{
	// 关闭摄像头
	flag = false;
}

void CMFCApplication3Dlg::OnStnClickedStatic1()
{
	// TODO: 在此添加控件通知处理程序代码
}
